/*
 * RemoteValidator.java    ver   03/dic/2009
 *
 * Copyright (c) 2009 mentalsmash.org
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * The use of the Apache License does not indicate that this project is
 * affiliated with the Apache Software Foundation.
 */
package org.mentalsmash.tazio.transactions.management.wrm;

import java.rmi.RemoteException;

import org.mentalsmash.tazio.net.ril.exceptions.UnavailableRemoteObjectException;
import org.mentalsmash.tazio.transactions.management.StatusResponse;
import org.mentalsmash.tazio.transactions.management.TazioTransaction;
import org.mentalsmash.tazio.transactions.management.rm.RMTransactionsFacade;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.inject.Inject;
import com.google.inject.name.Named;

/**
 * @version ver 03/dic/2009
 * @author Andrea Reale <andrea.reale@gmail.com>
 * 
 */
class RemoteValidator implements RMValidator {

    private final static Logger log = LoggerFactory
	    .getLogger(RemoteValidator.class);

   

    private final int waitBeforeRetryActiveTimeout;
    private final int waitBeforeRetryUnknownTimoout;
    private final int numRetries;

    private final RMTransactionsFacade rmFacade;

    private final Object waitOnMe = new Object();

    @Inject
    protected RemoteValidator(RMTransactionsFacade facade,
	    @Named("RMValidatorWaitActive") int waitBeforeRetryActive,
	    @Named("RMValidatorWaitUnknown") int waitBeforeRetryUnknown,
	    @Named("RMValidatorNumRetries") int numRetries) {

	this.rmFacade = facade;
	this.waitBeforeRetryActiveTimeout = waitBeforeRetryActive;
	this.waitBeforeRetryUnknownTimoout = waitBeforeRetryUnknown;
	this.numRetries = numRetries;

    }

    /*
     * (non-Javadoc)
     * 
     * @seeorg.mentalsmash.tazio.transactions.management.wrm.RMValidator#
     * globallyValidateAndCommit
     * (org.mentalsmash.tazio.transactions.management.TazioTransaction)
     */
    @Override
    public long globallyValidateAndCommit(TazioTransaction tx) {
	long seqNo = -1;
	// calls the at most once commit operation
	try {
	    log.debug("Sending at most once commit request for tx {}", tx);
	    seqNo = rmFacade.commitTransaction(tx);
	} catch (UnavailableRemoteObjectException e) {
	    log.debug("At most once commit() failed for {}. Going with WATT",
		    tx);
	    return inspectTransactionOutcome(tx);
	}
	return seqNo;
    }

    private long inspectTransactionOutcome(TazioTransaction tx) {
	long outcome = -1; // initialized to rollback
	try {
	    StatusResponse res = rmFacade
		    .whatAboutThisTx(tx.getTransactionId());
	    int waitTimeout = -1;
	    int retriesCounter = 0;

	    switch (res.getStatus()) {
	    case COMMITTED:
		outcome = res.getSeqNo();
		break;
	    case ROLLED_BACK:
		outcome = -1;
		break;
	    case ACTIVE: // must wait a bit till becomes committed
		waitTimeout = waitBeforeRetryActiveTimeout;
		// falls back to the UNKNOWN case
	    case UNKNOWN:
		log.debug("Response to WATT was {}. Waiting a bit..", res
			.getStatus());
		if (waitTimeout < 0)
		    waitTimeout = waitBeforeRetryUnknownTimoout;
		boolean finalStatus = false;
		synchronized (waitOnMe) {

		    while (retriesCounter < numRetries && !finalStatus) {
			try {
			    waitOnMe.wait(waitTimeout);
			} catch (InterruptedException e) {
			    log.warn("Remote validator was interrupted", e);
			    continue;
			}
			retriesCounter++;
			res = rmFacade.whatAboutThisTx(tx.getTransactionId());
			finalStatus = res.getStatus().equals(
				StatusResponse.StatusResponseEnum.COMMITTED)
				|| res
					.getStatus()
					.equals(
						StatusResponse.StatusResponseEnum.ROLLED_BACK);
		    }

		}

		// If I exeeded the number of retries without getting a
		// final result
		if (retriesCounter == numRetries && !finalStatus) {
		    // return a rollback outcome
		    outcome = -1;
		} else {
		    if (res.getStatus() == StatusResponse.StatusResponseEnum.COMMITTED) {
			outcome = res.getSeqNo();
		    } else {
			outcome = -1;
		    }
		}

	    }

	} catch (UnavailableRemoteObjectException e1) {
	    log
		    .warn(
			    "WATT request faild. Unable to contact any WRM for committing {}",
			    tx);
	    outcome = -1;
	}

	return outcome;
    }
}
